This blog was written by Stanley Zhu.
The Windows kernel privilege escalation vulnerability CVE-2016-7255 has received a lot of media attention. On November’s Patch Tuesday, Microsoft released a fix for this vulnerability as part of bulletin MS16-135. CVE-2016-7255 was used to perform a targeted attack and a sample was found in the wild, according to Microsoft. Google and Microsoft have already confirmed the Russian hacker group APT28 used a Flash vulnerability (CVE-2016-7855) along with this kernel privilege escalation flaw to perform a targeted attack. Google has also discussed this vulnerability.
- https://security.googleblog.com/2016/10/disclosing-vulnerabilities-to-protect.html
- https://threatpost.com/microsoft-says-russian-apt-group-behind-zero-day-attacks/121722/
- http://securityaffairs.co/wordpress/53242/hacking/cve-2016-7255-zero-day.html
The vulnerability research team at McAfee Labs has spent a significant amount of time analyzing this vulnerability. In this post we will briefly discuss some of our findings.
We started our analysis with the patch of MS16-135, and very soon we noticed that MS16-135 updated win32k.sys on the target system. Our investigation continued with the comparison (via binary diffing) of the two win32k.sys files (before and after installing the patch). Our test system ran Windows 7 Version 6.1.7601.23584.
Looking at the binary diffing results, we noticed the following functions were modified.
Figure 1: The changed function xxxNextWindow in win32k.sys.
After some preliminary investigation we concluded the patch for CVE-2016-7255 was applied solely in the function xxxNextWindow in win32k.sys.
The following screenshot shows a very high-level overview of the changes made to xxxNextWindow(x,x):
Figure 2: High-level diffing results in the function xxxNextWindow.
We can see some new logic has been added (highlighted in red) to the middle of the patched function. Zooming into the first newly inserted basic block, we can see that the newly introduced code compares the value of eax+0x23.
Figure 3: The first inserted basic block in xxxNextWindow.
We see similar logic in next newly inserted basic block.
Figure 4: The second inserted basic block in xxxNextWindow.
Google has stated the vulnerability “can be triggered via the win32k.sys system call NtSetWindowLongPtr() for the index GWLP_ID on a window handle with GWL_STYLE set to WS_CHILD.”
In fact, NtSetWindowLongPtr only helps trigger this vulnerability, while the root cause lies in xxxNextWindow. More specifically, the inappropriate parameters set by NtSetWindowLongPtr can trigger an “arbitrary address write” scenario in xxxNextWindow.
Now let’s take a look at the decompiled version of the unpatched xxxNextWindow(x,x…).
Figure 5: The decompiled version of the unpatched xxxNextWindow.
After the patch is applied, xxxNextWindow (x,x…) looks like this:
Figure 6: The decompiled version of the patched xxxNextWindow.
The code after the patch has enhanced the parameter verification with the conditional branch statement “(*(_BYTE *)(v8 + 0x23) & 0xC0) != 0x40.”
In this new statement, variable v8 (in eax) is the return value of a previous GetNextQueueWindow call. (See following figure.)
Figure 7: Variable v8 comes from a call to GetNextQueueWindow: “v8 = _GetNextQueueWindow(v7, v31, 1);”
A quick look at the implementation of _GetNextQueueWindow(x,x,x,…) reveals that the function actually returns a pointer to the tagWND structure.
The following screen shows the tagWND structure in windbg:
Figure 8: The structure of tagWND.
Analyzing this code, we know the field at offset 0x78 in the tagWND structure is relevant to the vulnerability. The following lines of decompiled code from the unpatched function illustrate that the field at offset 0x78 is relevant to the vulnerability:
Figure 9: Problematic code in the unpatched xxxNextWindow.
Now the problem becomes simple: If we can control the value at v8+0x78, we will be able to write to an arbitrary address in kernel land, and this could potentially allow the elevation of privilege. Luckily, a user-mode API (NtSetWindowLongPtr) is available to set an arbitrary value in that position.
The following screen shot shows that the value (0x41414141) we passed to NtSetWindowLongPtr is reflected in the tagWND structure, making it easy to gain an arbitrary memory write through this vulnerability.
Figure 10: An arbitrary value is set in the tagWnd structure.
To to trigger the vulnerability, the WS_CHILD attribute of the newly created window must be assigned, and the GWLP_ID attribute must be set with the help of the API NtSetWindowLongPtr(). Moreover, the last hurdle is to trigger xxxNextWindow. After some research, we found we can trigger it by pressing a combination of Alt+Tab keys or simulating the key press with the keybd_event API.
Now that we understand the root cause of this vulnerability from the high level, let’s try reproducing the vulnerability. We will create a simple window and populate some values in its tagWND structure.
HWND hwnd = CreateWindowEx(0, L”TestWnd”, 0, WS_OVERLAPPEDWINDOW | WS_VISIBLE | WS_CHILD, 5, 5, 1, 1, hWndParent, 0/*hMenu */, h, 0);
SetWindowLongPtr(hwnd, GWLP_ID,/*0xfffffff4=GWLP_ID*/ 0x41414141);
Figure 11: Debugging the vulnerable function xxxNextWindow.
The preceding screenshot shows the live debugging output. Here the ebx register is holding the pointer to the tagWND structure, and a write violation will occur very soon. As you can see in the following figure, the destination of the offending instruction is just the address (adding 0x14) that we previously passed in via the NtSetWindowLongPtr API, and this perfectly illustrates an arbitrary address write attack.
Figure 12: Scenario for an arbitrary address write attack.
Let’s return to Microsoft’s patch, which starts by checking the value at offset 0x23 of the tagWND structure. In the patched code, we can see the newly introduced statement
(*(_BYTE *)(v8 + 0x23) & 0xC0) != 0x40
When it comes to the patched version of the function, ebx points to the tagWND of the structure ebx + 0x23 = 0x54;
0x54 & 0xc0 = 0x40 ;(1) , 0x40 != 0x40 (2) ;
Now this statement becomes false. Therefore, the program skips the following code lines that attempt to modify memory, and avoids the program crash (the write access violation).
*(_DWORD *)(*(_DWORD *)(v30 + 0x78) + 0x14) &= 0xFFFFFFFB;
*(_DWORD *)(*(_DWORD *)(v8 + 0x78) + 0x14) |= 4u;
How can this vulnerability be exploited to achieve a privilege escalation? Instead of allowing the writing of an arbitrary value to an arbitrary address, this vulnerability can change only one bit; that is, the value on the address will be logically OR-ed with 0x04 (or its multiples) as shown below:
Value = Value | 0x04;
Value = Value | 0x0400;
Value = Value | 0x040000
Value = Value | 0x04000000
In this case, if the attacker can find a certain array of objects in kernel land and enlarge the index of the objects array (such as tagWnd->cbWndExtra) with this logical OR primitive to cause an out-of-bound access, the attacker will be able to gain arbitrary address read/write ability from user mode (by using some user mode APIs). We currently know some exploitation skills of this kind, such as GetBitmapbits/SetBitmapbits (first discovered by KeenTeam) or SetWindowText/GetWindowText.
Today, privilege escalation using a kernel mode vulnerability is still the primary vector to break application sandboxes (Internet Explorer’s EPM or Edge’s AppContainer). This path has been well demonstrated by most successful in-the-wild exploits targeting Internet Explorer/Edge/Adobe Reader and Flash that we have seen. Against current versions of Windows, with multilayer defenses, escaping the sandbox with a kernel escalation of privilege is still the attacker’s first choice. KeUsermodeCallback used to be a very popular type of Windows kernel mode vulnerability that can lead to kernel mode code execution, as we saw in CVE-2014-4113 and CVE-2015-0057. Microsoft’s work on addressing kernel vulnerabilities and adding more mitigation security features has led to a decline in this type of attack. In response, attackers have begun to look into kernel font and GDI vulnerabilities. Windows 10 has already restricted win32k calls in Edge, which significantly reduces the attack surface. And Microsoft has also fixed the kernel memory information disclosure issue that leverages the GDI-shared handle table. No doubt, kernel exploitation will become more and more difficult. However, we foresee that attackers will still use win32k as the main attack surface to exploit the kernel to achieve code execution or elevation of privilege. The battle will continue around this hot spot for both attackers and defenders.
I thank my colleagues Bing Sun and Debasish Mandal for their help with this post.